import sys
import logging
from ctypes import *

from bcat.error import StdError

from bcat.ffi.error import LoadingError
from bcat.ffi.loader import SharedObjectsLoader


def _load_so(target_name):
    try:
        loader = SharedObjectsLoader.from_one(target_name).load()
        target_dll = loader.get(target_name)
        return loader, target_dll
    except LoadingError as e:
        logging.error(f"load dlls:{target_name} error:{e}")
        raise e


class CCrypto(object):
    def __init__(self):
        if sys.platform.startswith('win'):
            target_name = "libossl_enc.dll"
        else:
            target_name = "libossl_enc.so"
        (self.__loader, self.__target_dll) = _load_so(target_name)

    @staticmethod
    def _check_success(err_code, ctx):
        if err_code == 1:
            return True
        else:
            raise StdError(err_code, ctx)

    def sm4_enc(self, key_bin, plain_bin):
        if not isinstance(key_bin, bytes):
            raise TypeError("key must be bytes")
        if not isinstance(plain_bin, bytes):
            raise TypeError("plain must be bytes")

        self.__target_dll.sge_ossl_sm4_ECB_encrypt.argtypes = [c_void_p, c_ulong, c_void_p, c_ulong, c_void_p, c_void_p]
        self.__target_dll.sge_ossl_sm4_ECB_encrypt.restype = c_int

        buf_len = c_ulong(len(plain_bin) + 16)
        buf = create_string_buffer(buf_len.value)
        ret = self.__target_dll.sge_ossl_sm4_ECB_encrypt(cast(key_bin, c_void_p), len(key_bin),
                                                         cast(plain_bin, c_void_p),
                                                         len(plain_bin), buf, byref(buf_len))
        self._check_success(ret, "sge_ossl_sm4_ECB_encrypt")
        return buf[:buf_len.value]

    def sm4_dec(self, key_bin, cipher_bin):
        if not isinstance(key_bin, bytes):
            raise TypeError("key must be bytes")
        if not isinstance(cipher_bin, bytes):
            raise TypeError("cipher must be bytes")

        self.__target_dll.sge_ossl_sm4_ECB_decrypt.argtypes = [c_void_p, c_ulong, c_void_p, c_ulong, c_void_p, c_void_p]
        self.__target_dll.sge_ossl_sm4_ECB_decrypt.restype = c_int

        buf_len = c_ulong(len(cipher_bin))
        buf = create_string_buffer(buf_len.value)
        ret = self.__target_dll.sge_ossl_sm4_ECB_decrypt(cast(key_bin, c_void_p), len(key_bin),
                                                         cast(cipher_bin, c_void_p), len(cipher_bin), buf,
                                                         byref(buf_len))
        self._check_success(ret, "sge_ossl_sm4_ECB_encrypt")
        return buf[:buf_len.value]

    def sm2_enc(self, pubkey, data_bin):
        if not isinstance(pubkey, str):
            raise TypeError("pubkey must be str")
        if not isinstance(data_bin, bytes):
            raise TypeError("data must be bytes")

        self.__target_dll.sge_ossl_sm2_encrypt.argtypes = [c_char_p, c_void_p, c_ulong, c_void_p, c_void_p]
        self.__target_dll.sge_ossl_sm2_encrypt.restype = c_int
        buf_len = c_int(128 + len(data_bin))
        buf = create_string_buffer(buf_len.value)
        ret = self.__target_dll.sge_ossl_sm2_encrypt(pubkey.encode(), data_bin, len(data_bin), buf, byref(buf_len))
        self._check_success(ret, "sm2_enc")
        return buf[:buf_len.value]

    def sm3_sm2_sign(self, prikey_uri, data_bin):
        if not isinstance(prikey_uri, str):
            raise TypeError("prikey_uri must be str")
        if not isinstance(data_bin, bytes):
            raise TypeError("data must be bytes")

        self.__target_dll.sge_ossl_sign.argtypes = [c_char_p, c_void_p, c_ulong, c_void_p, c_void_p]
        self.__target_dll.sge_ossl_sign.restype = c_int
        buf_len = c_int(128)
        buf = create_string_buffer(buf_len.value)
        ret = self.__target_dll.sge_ossl_sign(prikey_uri.encode(), data_bin, len(data_bin), buf, byref(buf_len))
        self._check_success(ret, "sm3_sm2_sign")
        return buf[:buf_len.value]


class CCryptoApiInstance:
    __instance = None

    @staticmethod
    def get():
        if CCryptoApiInstance.__instance is None:
            CCryptoApiInstance.__instance = CCrypto()
        return CCryptoApiInstance.__instance
